body.ts 2.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081
  1. import { createReadStream } from 'node:fs'
  2. import { pipeline } from 'node:stream/promises'
  3. import { type NextApiRequest, type NextApiResponse } from 'next'
  4. import apiWrapper from '@/lib/api/apiWrapper'
  5. import { getFunctionsArtifactStore } from '@/lib/api/self-hosted/functions'
  6. import { uuidv4 } from '@/lib/helpers'
  7. export default function handlerWithErrorCatching(req: NextApiRequest, res: NextApiResponse) {
  8. return apiWrapper(req, res, handler, { withAuth: true })
  9. }
  10. async function handler(req: NextApiRequest, res: NextApiResponse) {
  11. const { method } = req
  12. switch (method) {
  13. case 'GET':
  14. return handleGet(req, res)
  15. default:
  16. res.setHeader('Allow', ['GET'])
  17. res.status(405).json({ data: null, error: { message: `Method ${method} Not Allowed` } })
  18. }
  19. }
  20. async function handleGet(req: NextApiRequest, res: NextApiResponse) {
  21. const slugParam = req.query.slug
  22. const slug = Array.isArray(slugParam) ? slugParam[0] : slugParam
  23. if (!slug) {
  24. res.status(404).json({ error: { message: `Missing function 'slug' parameter` } })
  25. return
  26. }
  27. const store = getFunctionsArtifactStore()
  28. const fileEntries = await store.getFileEntriesBySlug(slug)
  29. const boundary = `----FormBoundary${uuidv4().replace(/-/g, '')}`
  30. const totalSize = fileEntries.reduce((sum, entry) => sum + entry.size, 0)
  31. const metadata = {
  32. // mock id, should be "<project_id>_<function_id>_<version>"
  33. deployment_id: uuidv4(),
  34. original_size: totalSize,
  35. compressed_size: totalSize,
  36. module_count: fileEntries.length,
  37. }
  38. res.setHeader('Content-Type', `multipart/form-data; boundary=${boundary}`)
  39. res.status(200)
  40. // Write metadata part
  41. const metadataJson = JSON.stringify(metadata)
  42. res.write(
  43. `--${boundary}\r\n` +
  44. `Content-Disposition: form-data; name="metadata"\r\n` +
  45. `Content-Type: application/json\r\n` +
  46. `\r\n` +
  47. metadataJson +
  48. `\r\n`
  49. )
  50. // Stream each file part
  51. for (const entry of fileEntries) {
  52. const safeName = entry.relativePath
  53. .replace(/[\r\n]/g, '')
  54. .replace(/\\/g, '\\\\')
  55. .replace(/"/g, '\\"')
  56. const encodedName = encodeURIComponent(entry.relativePath)
  57. res.write(
  58. `--${boundary}\r\n` +
  59. `Content-Disposition: form-data; name="file"; filename="${safeName}"; filename*=UTF-8''${encodedName}\r\n` +
  60. `Content-Type: text/plain\r\n` +
  61. `\r\n`
  62. )
  63. await pipeline(createReadStream(entry.absolutePath), res, { end: false })
  64. res.write(`\r\n`)
  65. }
  66. // Write closing boundary
  67. res.write(`--${boundary}--\r\n`)
  68. res.end()
  69. }